#include "loginlogical.h"
#include "src/common/localsetting.h"
#include "src/utils/log.h"
#include <QProcess>
#include <QDebug>

#include <chrono>
#include "src/authenticate/faceauthenticate_cv.h"
//#include "src/authenticate/faceauthenticate_cw.h"

// 5次--3min；6次--6min；7次--9min；8次--18min；9次--1h；10次--24h； 超过10次后，每错误1次，都将账户锁定24h
static const std::vector<quint32> g_time_delay = {3, 6, 9, 18, 60, 24*60};
static const quint32 g_face_regist_count = 10;
static const quint32 g_face_predict_shot = 1;
static const quint32 g_face_predict_threshold = 20;

Loginlogical::Loginlogical(QObject *parent)
    : QThread{parent}
{
    m_faceAPI = new FaceAuthenticateCV(this);
//    m_faceAPI = new FaceAuthenticateCW(this);
    m_cap = new cv::VideoCapture();
    m_cap->set(cv::CAP_PROP_FRAME_WIDTH, 640);
    m_cap->set(cv::CAP_PROP_FRAME_HEIGHT, 480);
    m_play = false;

    LocalSetting* settings = LocalSetting::instance();
    m_faceAPI->initFromModelPath(settings->getFaceRecogFilepath(), settings->getFaceDetectFilepath());
}

Loginlogical::~Loginlogical()
{
    delete m_cap;
    delete m_faceAPI;
}

QString Loginlogical::getSystemUsername()
{
    QString username;
#ifdef Q_OS_WIN
    QProcess process;
    process.start("cmd.exe", QStringList() << "/c" << "echo %username%");
    process.waitForFinished();
    username = QString::fromLocal8Bit(process.readAllStandardOutput().trimmed());
#elif defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
    QProcess process;
    process.start("whoami");
    process.waitForFinished();
    username = QString::fromLocal8Bit(process.readAllStandardOutput()).trimmed();
#endif
    return username;
}

// 账户已锁定，请3分钟后再尝试
// 【24小时56分钟】【3分钟】【2分钟】【59秒】
QString Loginlogical::getSecondsString(quint32 seconds)
{
    if (seconds < 60)
        return QString("%1秒").arg(seconds);
    quint32 minutes = seconds / 60;
    quint32 hours = minutes / 60;
    minutes = minutes % 60;

    if (hours > 0)
        return QString("%1小时%2分钟").arg(hours).arg(minutes);

    return QString("%1分钟").arg(minutes + (seconds % 60 == 0 ? 0 : 1));
}

quint32 Loginlogical::getCanRetryCount()
{
    LocalSetting* settings = LocalSetting::instance();
    quint32 lastFailedCount = settings->getLastLoginFailedCount();
    return lastFailedCount;
}

quint32 Loginlogical::getLockRetryTimedelay()
{
    LocalSetting* settings = LocalSetting::instance();
    quint32 lastFailedCount = settings->getLastLoginFailedCount();
    QDateTime lastFailedDatetime = settings->getLastLoginDatetime();
    QDateTime curr = QDateTime::currentDateTime();
    qint64 secsTo = lastFailedDatetime.secsTo(curr);

    if (lastFailedCount < 5)
        return 0;

    switch (lastFailedCount)
    {
    case 5:
        if (secsTo < 60 * g_time_delay[0])
            return 60 * g_time_delay[0] - secsTo;
        return 0;
    case 6:
        if (secsTo < 60 * g_time_delay[1])
            return 60 * g_time_delay[1] - secsTo;
        return 0;
    case 7:
        if (secsTo < 60 * g_time_delay[2])
            return 60 * g_time_delay[2] - secsTo;
        return 0;
    case 8:
        if (secsTo < 60 * g_time_delay[3])
            return 60 * g_time_delay[3] - secsTo;
        return 0;
    case 9:
        if (secsTo < 60 * g_time_delay[4])
            return 60 * g_time_delay[4] - secsTo;
        return 0;
    case 10:
    default:
        if (secsTo < 60 * g_time_delay[5])
            return 60 * g_time_delay[5] - secsTo;
        return 0;
    }
    return 0;
}

#ifdef USE_OPENCV
QImage Loginlogical::matToQImage(const cv::Mat& mat)
{
    switch (mat.type())
    {
        case CV_8UC1:
        {
            QImage img(mat.data, mat.cols, mat.rows, mat.cols, QImage::Format_Grayscale8);
            return img;
        }
        case CV_8UC3:
        {
            QImage img(mat.data, mat.cols, mat.rows, mat.cols * 3, QImage::Format_RGB888);
            return img.rgbSwapped();
        }
        case CV_8UC4:
        {
            QImage img(mat.data, mat.cols, mat.rows, mat.cols * 4, QImage::Format_ARGB32);
            return img;
        }
        default:
        {
            return QImage();
        }
    }
    QImage();
}

void Loginlogical::stopThreadCapturing()
{
    m_play = false;
    if (isRunning())
    {
        wait();
    }
    if (!m_mat.empty())
        m_mat.release();

    if (m_cap && m_cap->isOpened())
        m_cap->release();
    qDebug() << "camera released ";
}

void Loginlogical::removeFaceLabel(const QString& label)
{
    m_faceAPI->removeFaceLabel(label);
}

bool Loginlogical::editFaceLabel(const QString& label, const QString& newLabel)
{
    return m_faceAPI->editFaceLabel(label, newLabel);
}

QString Loginlogical::generateFaceLabel(const QList<QString>& existLables)
{
    for (quint32 i = 1; i< 10;i++)
    {
        QString newFaceLabel = QString("面容ID%1").arg(i, 1, 10, QChar('0'));
        bool exists = false;
        for (const QString& existLabel : existLables)
        {
            if (existLabel == newFaceLabel)
            {
                exists = true;
                break;
            }
        }
        if (!exists)
            return newFaceLabel;
    }
    return QString();
}


Loginlogical::Face_Regist_Result Loginlogical::faceRegist(QString label)
{
    vector<Mat> validFaces;
    Mat mat_withrect;
    Mat mat_gray;
    auto validFacetime = std::chrono::system_clock::now();
    auto startFacetime = validFacetime;

    if (!(m_cap && m_cap->isOpened()))
        return Face_Regist_Failed_Nocamera;

    while (m_play && m_cap->isOpened())
    {
        auto current = std::chrono::system_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(current - validFacetime);
        auto totalduration = std::chrono::duration_cast<std::chrono::milliseconds>(current - startFacetime);

        // 间隔超20秒
        if (duration.count() >= 20*1000)
            return Face_Regist_Failed_Timeout;

        // 间隔10秒提示
        if (duration.count() >= 10*1000)
            emit sigFaceRegistLongtimenoface();

        // 累计不超过30秒
        if (totalduration.count() >= 30*1000)
            return Face_Regist_Failed_Timeout;

        bool ret = m_cap->read(m_mat);
        if(!ret)
        {
            CRITICAL("read camera failed !");
            return Face_Regist_Failed_Nocamera;
        }

        cv::Rect rect;
        FaceAuthInterface::FaceAuthResult res = m_faceAPI->face_detect_ex(m_mat, mat_gray, mat_withrect, rect);
        emit sigFacePreviewImage(matToQImage(mat_withrect));

        if ( res == FaceAuthInterface::FaceAuth_OK)
        {
            if (duration.count() > 800 && rect.area() >= 0.1 * m_mat.cols * m_mat.rows)
            {
//                INFO("image with rect " << rect.width << "x" << rect.height <<
//                     " image:" << m_mat.cols << "x" << m_mat.rows <<
//                     " percent: " << 1.0 * rect.area() / ( m_mat.cols * m_mat.rows) );
                validFaces.push_back(mat_gray);
                validFacetime = current;
                if (validFaces.size() >= g_face_regist_count)
                    break;

                emit sigFaceRegistProcessing(validFaces.size(), g_face_regist_count);
            }
        }
        else
        {
            // INFO("face detect error " << res);
            // invalid : noface /multiface /Init_Failed
        }
        QThread::msleep(1);
    }

    if (validFaces.size() >= g_face_regist_count && m_play)
    {
        m_faceAPI->face_regist_images(label, validFaces);
        return Face_Regist_Success;
    }
    return Face_Regist_Failed_Canceled;
}

Loginlogical::Face_Predict_Result Loginlogical::facePredict()
{
    quint32 validFaceCount = 0;
    Mat mat_withrect;
    Mat mat_gray;
    auto validFacetime = std::chrono::system_clock::now();

    if (!(m_cap && m_cap->isOpened()))
        return Face_Predict_Failed_Nocamera;

    while (m_play && m_cap->isOpened())
    {
        auto current = std::chrono::system_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(current - validFacetime);

        if (duration.count() >= 10*1000)
            return Face_Predict_Failed_Timeout;

        bool ret = m_cap->read(m_mat);
        if(!ret)
        {
            CRITICAL("read camera failed !");
            return Face_Predict_Failed_Nocamera;
        }

        if (duration.count() <= 400)
        {
//            emit sigFacePreviewImage(matToQImage(m_mat));
            continue;
        }

        cv::Rect rect;
        FaceAuthInterface::FaceAuthResult res = m_faceAPI->face_detect_ex(m_mat, mat_gray, mat_withrect, rect);

        if ( res == FaceAuthInterface::FaceAuth_OK)
        {
//            INFO("image with rect " << rect.width << "x" << rect.height <<
//                 " image:" << m_mat.cols << "x" << m_mat.rows <<
//                 " percent: " << 1.0 * rect.area() / ( m_mat.cols * m_mat.rows) );

            if (duration.count() > 400 && rect.area() >= 0.1 * m_mat.cols * m_mat.rows)
            {
                FaceAuthInterface::FaceRecogResult result;
                m_faceAPI->face_predict_ex(mat_gray, result);
                if (result.confidence <= g_face_predict_threshold)
                {
                    validFaceCount++;
                    validFacetime = current;
                    INFO("face predict as:" << result.key << " confidence:"<< result.confidence);

                    if (validFaceCount >= g_face_predict_shot)
                        return Face_Predict_Success;
                }
                else
                {
                    INFO("face predict nopass as :"<< result.key << " confidence:"<< result.confidence);
                }
            }
        }
        else
        {
            // INFO("face detect error " << res);
            // invalid : noface /multiface /Init_Failed
        }
        QThread::msleep(1);
    }
    return Face_Predict_Failed_Timeout;
}
#endif

Loginlogical::Login_Result Loginlogical::login(const QString& uname, const QString&passwd, QString& resultMsg)
{
    LocalSetting* settings = LocalSetting::instance();
    quint32 lastFailedCount = settings->getLastLoginFailedCount();
    QDateTime curr = QDateTime::currentDateTime();

    quint32 retrySeconds = getLockRetryTimedelay();
    if (lastFailedCount >= 5 && retrySeconds > 0)
    {
//        settings->setLastLoginFailedCount(lastFailedCount+1);
//        settings->setLastLoginDatetime(curr);
        resultMsg = "账户已锁定"; // 账户已锁定，请3分钟后再尝试
        qDebug() << "login failed already locked " << lastFailedCount << retrySeconds;
        return Login_Failed_Locked;
    }

#ifndef _WIN32
    bool authResult = m_AuthModule.pam_module_init(uname.toStdString().c_str());
    if (!authResult)
    {
        settings->setLastLoginFailedCount(lastFailedCount+1);
        settings->setLastLoginDatetime(curr);

        qDebug() << "pam_module_init failed ! " << uname;
        m_AuthModule.pam_module_finit();
        return Login_Failed_AcctpwdError;
    }
    authResult = m_AuthModule.pam_module_auth(passwd.toStdString().c_str());
    if (!authResult)
    {
        settings->setLastLoginFailedCount(lastFailedCount+1);
        settings->setLastLoginDatetime(curr);

        qDebug() << "pam_authenticate failed ! " << uname;
        m_AuthModule.pam_module_finit();
        return Login_Failed_AcctpwdError;
    }
    m_AuthModule.pam_module_finit();
#else
    if (passwd != "xxx")
    {
        settings->setLastLoginFailedCount(lastFailedCount+1);
        settings->setLastLoginDatetime(curr);
        return Login_Failed_AcctpwdError;
    }
#endif

    settings->removeLastloginDatetimeCount();
    return Login_Success;
}


void Loginlogical::loginThread(const QString& uname, const QString& passwd)
{
    m_auth_type = Login_Auth_Account;
    m_uname = uname;
    m_passwd = passwd;
    start();
}

void Loginlogical::faceRegistThread(int cameraIndex) //const QString& camera);
{
    m_auth_type = Login_Auth_FaceRegist;
    m_cameraIndex = cameraIndex;
#ifdef USE_OPENCV
    m_play = true;
    m_cap->open(m_cameraIndex);
    qDebug() << "regist camera open : " << m_cap->isOpened();
    start();
#else
    qDebug() << "open camera " << m_cameraId;
    if (curCamera!= nullptr)
        delete curCamera;
    curCamera = new QCamera(m_cameraId.toLocal8Bit(), this);
    curCamera->setCaptureMode(QCamera::CaptureStillImage);

    if (imageCapture != nullptr)
        delete imageCapture;
    imageCapture = new QCameraImageCapture(curCamera, this) ;
    imageCapture->setBufferFormat(QVideoFrame::Format_Jpeg);
    imageCapture->setCaptureDestination (QCameraImageCapture::CaptureToBuffer);

    connect(imageCapture, &QCameraImageCapture::imageCaptured, this, &Loginlogical::onFaceregistImageCaptured);

    curCamera->start();
#endif
}

void Loginlogical::onFaceregistImageCaptured(int id, const QImage & image)
{
    // api face_regist

    emit sigFacePreviewImage(image);
}

void Loginlogical::facePredictThread(int cameraIndex) //const QString& camera);
{
    m_auth_type = Login_Auth_Face;
    m_cameraIndex = cameraIndex;
    m_play = true;
    m_cap->open(m_cameraIndex);
    qDebug() << "predict camera open : " << m_cap->isOpened();
    start();
}


void Loginlogical::run()
{
    if (m_auth_type == Login_Auth_Account)
    {
        QString resMsg;
        Login_Result res;
        res = login(m_uname, m_passwd, resMsg);
        emit sigLoginResult(res, resMsg);
        return;
    }
    else if (m_auth_type == Login_Auth_FaceRegist)
    {
        Face_Regist_Result res;
        QList<QString> labels = m_faceAPI->getLabels();
        QString genLabel = generateFaceLabel(labels);
        INFO("generate new label for regist : " << genLabel);
        res = faceRegist(genLabel);
        emit sigFaceRegistResult(res, genLabel);
        qDebug() << "camera regist thread finied ";
        return;
    }
    else if (m_auth_type == Login_Auth_Face)
    {
        Face_Predict_Result res;
        res = facePredict();
        emit sigFacePredictResult(res);
        qDebug() << "camera predict thread finied ";
        return;
    }
}
